package xq.gwt.ui.widgets.entity;

import java.util.ArrayList;
import java.util.HashMap;
import xq.gwt.mvc.model.BooleanPropertyModel;
import xq.gwt.mvc.model.CommandModel;
import xq.gwt.mvc.model.EntityModel;
import xq.gwt.mvc.model.IntegerPropertyModel;
import xq.gwt.mvc.model.PersistableEntityModel;
import xq.gwt.mvc.model.PropertyChangedEvent;
import xq.gwt.mvc.model.PropertyChangedListener;
import xq.gwt.mvc.model.PropertyModel;
import xq.gwt.ui.widgets.feedback.FeedbackService;

import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.DataSourceField;
import com.smartgwt.client.data.fields.DataSourceBooleanField;
import com.smartgwt.client.data.fields.DataSourceIntegerField;
import com.smartgwt.client.data.fields.DataSourceTextField;
import com.smartgwt.client.types.AutoFitWidthApproach;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.grid.ListGrid;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.CellClickEvent;
import com.smartgwt.client.widgets.grid.events.CellClickHandler;
import com.smartgwt.client.widgets.grid.events.CellSavedEvent;
import com.smartgwt.client.widgets.grid.events.CellSavedHandler;
import com.smartgwt.client.widgets.grid.events.DataArrivedEvent;
import com.smartgwt.client.widgets.grid.events.DataArrivedHandler;
import com.smartgwt.client.widgets.grid.events.RecordDoubleClickEvent;
import com.smartgwt.client.widgets.grid.events.RecordDoubleClickHandler;
import com.smartgwt.client.widgets.grid.events.SelectionChangedHandler;
import com.smartgwt.client.widgets.grid.events.SelectionEvent;

public class EntityArrayPresenter {
	protected ListGrid entityGrid;
	protected EntityModel[] entities;	
	private EntitySelectedListener entitySelectedListener;
	private DataSource ds;
	private EntityModel prototypeEntity;
	private HashMap<String, ListGridRecord> recordMap;
	private ArrayList<CommandModel> delayedCommands = new ArrayList<CommandModel>();
	private ArrayList<PropertyClickedListener> propertyClickedListeners = new ArrayList<PropertyClickedListener>();
	private ArrayList<EntityDoubleClickedListener> entityDblClickedListeners = new ArrayList<EntityDoubleClickedListener>();
	private boolean blockChangeNotifications;
	
	public EntityArrayPresenter() {
		entityGrid = buildGrid();
		init();
	}
	
	public EntityArrayPresenter(ListGrid listGrid){
		entityGrid = listGrid;
		init();
	}
	
	public EntityArrayPresenter(EntityModel prototypeEntity) {
		entityGrid = buildGrid();
		this.prototypeEntity = prototypeEntity;
		init();
	}
	
	public EntityArrayPresenter(ListGrid listGrid, EntityModel prototypeEntity){
		this.prototypeEntity = prototypeEntity;
		entityGrid = listGrid;
		init();
	}
		
	public EntityModel getSelectedEntity(){
	
		ListGridRecord selectedRecord = entityGrid.getSelectedRecord();
		if(selectedRecord != null){
			EntityRecord record = (EntityRecord)selectedRecord;
			return record.entity;
		}
		return null;
	}
	
	private ListGrid buildGrid(){
		ListGrid listGrid = new ListGrid();
		listGrid.setAutoFitWidthApproach(AutoFitWidthApproach.BOTH);
		listGrid.setAutoFitFieldWidths(true);
		return listGrid;
	}
	
	protected void init(){
		entityGrid.setWidth("100%");
		entityGrid.setHeight("100%");		
		entityGrid.setSaveLocally(true);
		
		entityGrid.addSelectionChangedHandler(new SelectionChangedHandler() {
			
			@Override
			public void onSelectionChanged(SelectionEvent event) {
				EntityRecord record = (EntityRecord)event.getRecord(); 
				handleEntitySelected(record.getEntity());
				
			}
		});		
				
		if (prototypeEntity != null){
			initDataSource();
			initFields();
			entityGrid.setDataSource(ds);
		}
		
		entityGrid.addDataArrivedHandler(new DataArrivedHandler() {
			
			@Override
			public void onDataArrived(DataArrivedEvent event) {
				for (int i = 0; i < delayedCommands.size(); i++) {
					try {
						delayedCommands.get(i).execute();
					} catch (Exception e) {
						e.printStackTrace();
					}					
				}
				delayedCommands.clear();				
			}
		});
		
		
		entityGrid.addCellSavedHandler(new CellSavedHandler() {
			
			@Override
			public void onCellSaved(CellSavedEvent event) {
				if(!blockChangeNotifications){
					EntityRecord record = (EntityRecord)event.getRecord();
					String propertyName = entityGrid.getField(event.getColNum()).getName();
					record.updateModel(propertyName);
				}
			}
		});
		
		entityGrid.addCellClickHandler(new CellClickHandler() {
			
			@Override
			public void onCellClick(CellClickEvent event) {
				if(propertyClickedListeners.size() != 0){
					EntityRecord record = (EntityRecord)event.getRecord();
					String propertyName = entityGrid.getField(event.getColNum()).getName();					
					notifyPropertyClicked(propertyName, record.getEntity());
				}				
			}
		});
		
		entityGrid.addRecordDoubleClickHandler(new RecordDoubleClickHandler() {
			
			@Override
			public void onRecordDoubleClick(RecordDoubleClickEvent event) {
				EntityRecord record = (EntityRecord)event.getRecord();
				notifyEntityDoubleClicked(record.entity);
			}
		});
	}
	
	protected void initDataSource(){
		ds = new DataSource();
		ds.setClientOnly(true);
	}
	
	private void handleEntitySelected(EntityModel entity){
		if(entitySelectedListener == null)
			return;
		entitySelectedListener.entitySelected(entity);
	}
		
	public void setEntities(EntityModel[] entities){		
		handleEntitySelected(null);
		recordMap = new HashMap<String, ListGridRecord>();
		entityGrid.invalidateCache();
		this.entities = entities;
		if(this.entities == null)
			return;
				
		if(ds == null){
			initDataSource();
			if(this.entities.length != 0){
				prototypeEntity = this.entities[0];
				initFields();
			}			
		}
		
		ListGridRecord[] records = new ListGridRecord[this.entities.length];
		for (int i = 0; i < entities.length; i++) {			 
			records[i] = new EntityRecord(entities[i]);			
			recordMap.put(entities[i].getIdentifier(), records[i]);
		}
		
		ds.setTestData(records);		
		if(entityGrid.getDataSource() == null){
			entityGrid.setDataSource(ds);
		}
		
		entityGrid.fetchData();
		
	}
			
	public void setSelectionListener(EntitySelectedListener listener) {
		this.entitySelectedListener = listener;
	}
	
	public void selectRecord(String identifier){
		delayedCommands.add(new selectRecordCommand(identifier));		
	}
	
	public boolean containsRecord(String identifier){
		if (recordMap == null)
			return false;
		
		return recordMap.containsKey(identifier);
	}
	
	private void initFields(){		
		if(prototypeEntity == null)
			return;
		PropertyModel[] props = prototypeEntity.getProperties();
		
		if(props == null)
			return;		
		
		//Make fields
		DataSourceField[] dsFields = new DataSourceField[props.length];
		for (int i = 0; i < props.length; i++) {
			dsFields[i] = buildDatasourceField(props[i]);
			dsFields[i].setHidden(props[i].isHidden());
			dsFields[i].setCanEdit(!props[i].isReadonly());
		}
		
		ds.setFields(dsFields);
		//set primary key if any
		if (prototypeEntity instanceof PersistableEntityModel){
			PropertyModel[] keyProps = ((PersistableEntityModel)prototypeEntity).getKeyProperties();
			if(keyProps != null){
				for (int i = 0; i < keyProps.length; i++) {
					try {
						ds.getField(keyProps[i].getName()).setPrimaryKey(true);
					} catch (Exception e) {
						e.printStackTrace();
					}					
				}
			}
		}
		
				
	}
	
	public Canvas getCanvas(){
		return entityGrid;
	}
	
	public ListGrid getGrid(){
		return entityGrid;
	}
	
	public class EntityRecord extends ListGridRecord implements PropertyChangedListener{
		private EntityModel entity;
		
		
		public EntityRecord(EntityModel entity) {
			removePropertyChangedListeners();
			this.entity = entity;			
			PropertyModel[] props = this.entity.getProperties();			
			if(props != null){
				for (int i = 0; i < props.length; i++) {					
					if(props[i].getPropertyType().equals(Boolean.class)){
						setAttribute(props[i].getName(), props[i].getObjectValue());
					} else {
						setAttribute(props[i].getName(), props[i].getText());
					}
				}
			}
			addPropertyChangedListeners();
		}

		public EntityModel getEntity() {
			return entity;
		}

		private void removePropertyChangedListeners(){
			if (this.entity == null)
				return;
			if (entity.getProperties() == null)
				return;
			for (int i = 0; i < entity.getProperties().length; i++) {
				entity.getProperties()[i].removePropertyChangedListener(this);
			}
		}
		
		private void addPropertyChangedListeners(){
			if (this.entity == null)
				return;
			if (entity.getProperties() == null)
				return;
			for (int i = 0; i < entity.getProperties().length; i++) {
				entity.getProperties()[i].addPropertyChangedListener(this);
			}
		}
		
		
		@Override
		public void propertyChanged(PropertyChangedEvent event) {
			if(blockChangeNotifications){
				return;
			}
			PropertyModel changedProperty = (PropertyModel)event.getSource();
			updatePropertyView(changedProperty);			
		}
		
		public void updatePropertyView(PropertyModel changedProperty){
			blockChangeNotifications = true;
			
			if(changedProperty != null){
				if(changedProperty.getPropertyType().equals(Boolean.class)){
					setAttribute(changedProperty.getName(), changedProperty.getObjectValue());
				} else {
					setAttribute(changedProperty.getName(), changedProperty.getText());
				}
								
				entityGrid.redraw();				
			}
			blockChangeNotifications = false;
		}
		
		public void updateModel(String propertyName){
			if(entity == null){
				return;
			}
			blockChangeNotifications = true;
			PropertyModel prop = this.entity.getProperty(propertyName);
			if(prop != null){
				String newVal = getAttribute(prop.getName());
				try {					
					prop.setText(newVal);
				} catch (Exception e) {
					FeedbackService.getFeedbackPresenter().showFeedback(e.toString());
				}				
			}
			blockChangeNotifications = false;
		}
	}
	
	private class selectRecordCommand implements CommandModel{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1437832505554193547L;
		private BooleanPropertyModel enabled = new BooleanPropertyModel();
		private String identifier;
		public selectRecordCommand(String identifier){
			this.identifier = identifier;
			enabled.setValue(true);
			
		}
		
		@Override
		public void execute() throws Exception {
			if((identifier == null) || (!recordMap.containsKey(identifier))){
				entityGrid.deselectAllRecords();
				return;
			}
			ListGridRecord record = recordMap.get(identifier);
			entityGrid.selectRecord(record);
			
		}

		@Override
		public String getCaption() {			
			return "Select";
		}

		@Override
		public String getName() {			
			return "Select";
		}

		@Override
		public BooleanPropertyModel isEnabled() {			
			return enabled;
		}

		@Override
		public String getFeedback() {			
			return null;
		}
		
	}

	public void clear() {
		entityGrid.invalidateCache();
		entityGrid.destroy();
		ds.destroy();
		
	}
	
	private DataSourceField buildDatasourceField(PropertyModel prop){
		
		if(prop instanceof IntegerPropertyModel){
			return new DataSourceIntegerField(prop.getName(),prop.getTitle());
		} 
		
		if(prop instanceof BooleanPropertyModel){
			return new DataSourceBooleanField(prop.getName(),prop.getTitle());
		}
				
		return new DataSourceTextField(prop.getName(),prop.getTitle());		
	}
	
	public void addPropertyClickedListener(PropertyClickedListener listener){
		propertyClickedListeners.add(listener);
	}
	
	public void removePropertyClickedListener(PropertyClickedListener listener){
		propertyClickedListeners.remove(listener);
	}
	
	protected void notifyPropertyClicked(String propName, EntityModel entity){
		for (PropertyClickedListener listener : propertyClickedListeners) {
			listener.onPropertyClicked(propName, entity);
		}
	}
	
	
	protected void notifyEntityDoubleClicked(EntityModel entity){
		for (EntityDoubleClickedListener listener : entityDblClickedListeners) {
			listener.onEntityDoubleClicked(entity);
		}
	}
	
	public void addEntityDblClickedListener(EntityDoubleClickedListener listener){
		entityDblClickedListeners.add(listener);
	}
	
	public void removeEntityDblClickedListener(EntityDoubleClickedListener listener){
		entityDblClickedListeners.remove(listener);
	}
}
